﻿using System.Collections.Generic;
using SpiceNet.Circuits;
using SpiceNet.Diagnostics;
using SpiceNet.Parameters;
using SpiceNet.Simulations;

namespace SpiceNet.Models
{
    /// <summary>
    /// A class that represents an instance of a current controlled current source
    /// </summary>
    public class CurrentControlledCurrentSource : Instance
    {
        /// <summary>
        /// Get parameters
        /// </summary>
        public override Dictionary<string, IP> ParamTable => _pTable;

        #region Parameters
        private enum _c
        {
            /* device parameters */
            CCCS_GAIN = 1,
            CCCS_CONTROL = 2,
            CCCS_POS_NODE = 3,
            CCCS_NEG_NODE = 4,
            CCCS_CONT_BR = 5,
            CCCS_GAIN_SENS = 6,
            CCCS_CURRENT = 7,
            CCCS_POWER = 8,
            CCCS_VOLTS = 9,

            /* device questions */
            CCCS_QUEST_SENS_REAL = 201,
            CCCS_QUEST_SENS_IMAG = 202,
            CCCS_QUEST_SENS_MAG = 203,
            CCCS_QUEST_SENS_PH = 204,
            CCCS_QUEST_SENS_CPLX = 205,
            CCCS_QUEST_SENS_DC = 206
        }
        private static Dictionary<string, IP> _pTable = new Dictionary<string, IP>
        {
            { "gain", new IP(IF.IOPU, (int)_c.CCCS_GAIN, T.REAL, "Gain of source") },
            { "control", new IP(IF.IOPU, (int)_c.CCCS_CONTROL, T.INSTANCE, "Name of controlling source") },
            { "sens_gain", new IP(IF.IP, (int)_c.CCCS_GAIN_SENS, T.FLAG, "flag to request sensitivity WRT gain") },
            { "neg_node", new IP(IF.OPU, (int)_c.CCCS_NEG_NODE, T.INTEGER, "Negative node of source") },
            { "pos_node", new IP(IF.OPU, (int)_c.CCCS_POS_NODE, T.INTEGER, "Positive node of source") },
            { "i", new IP(IF.OP, (int)_c.CCCS_CURRENT, T.REAL, "CCCS output current") },
            { "v", new IP(IF.OP, (int)_c.CCCS_VOLTS, T.REAL, "CCCS voltage at output") },
            { "p", new IP(IF.OP, (int)_c.CCCS_POWER, T.REAL, "CCCS power") },
            { "sens_dc", new IP(IF.OPU, (int)_c.CCCS_QUEST_SENS_DC, T.REAL, "dc sensitivity ") },
            { "sens_real", new IP(IF.OPU, (int)_c.CCCS_QUEST_SENS_REAL, T.REAL, "real part of ac sensitivity") },
            { "sens_imag", new IP(IF.OPU, (int)_c.CCCS_QUEST_SENS_IMAG, T.REAL, "imag part of ac sensitivity") },
            { "sens_mag", new IP(IF.OPU, (int)_c.CCCS_QUEST_SENS_MAG, T.REAL, "sensitivity of ac magnitude") },
            { "sens_ph", new IP(IF.OPU, (int)_c.CCCS_QUEST_SENS_PH, T.REAL, "sensitivity of ac phase") },
            { "sens_cplx", new IP(IF.OPU, (int)_c.CCCS_QUEST_SENS_CPLX, T.COMPLEX, "ac sensitivity") }
        };

        public Parameter<double> CCCScoeff { get; } = new Parameter<double>();
        public string CCCScontName { get; set; }
        public int CCCSsenParmNo { get; set; }
        #endregion
        
        /// <summary>
        /// Instance equation indices
        /// </summary>
        private int CCCSposNode;
        private int CCCSnegNode;
        private int CCCScontBranch;

        /// <summary>
        /// Constructor
        /// </summary>
        public CurrentControlledCurrentSource(string name) : base(name, 2, typeof(CurrentControlledCurrentSourceModel)) { }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">The name of the Current-Controlled Current source</param>
        /// <param name="nplus">The name of the positive output node</param>
        /// <param name="nminus">The name of the negative output node</param>
        /// <param name="vsource">The name of the voltage source through which the current is amplified</param>
        /// <param name="gain">The current gain (A/A)</param>
        public CurrentControlledCurrentSource(string name, string nplus, string nminus, string vsource, double gain) : this(name)
        {
            Connect(nplus, nminus);
            CCCScontName = vsource;
            CCCScoeff.Par(gain);
        }

        /// <summary>
        /// Set parameter
        /// </summary>
        protected override void Param(int id, object value, Circuit ckt = null)
        {
            switch ((_c)id)
            {
                case _c.CCCS_GAIN:
                    CCCScoeff.Par((double)value);
                    break;
                case _c.CCCS_CONTROL:
                    CCCScontName = (string)value;
                    break;
                case _c.CCCS_GAIN_SENS:
                    CCCSsenParmNo = (int)value;
                    break;
                default:
                    throw new BadParameterException();
            }
        }

        /// <summary>
        /// Ask a parameter
        /// </summary>
        protected override object Ask(int id, Circuit ckt = null)
        {
            switch ((_c)id)
            {
                case _c.CCCS_GAIN:
                    return CCCScoeff.Value;
                case _c.CCCS_CONTROL:
                    return CCCScontName;
                case _c.CCCS_POS_NODE:
                    return CCCSposNode;
                case _c.CCCS_NEG_NODE:
                    return CCCSnegNode;
                case _c.CCCS_CONT_BR:
                    return CCCScontBranch;
                case _c.CCCS_CURRENT:
                    if (ckt.CurrentAnalysis.HasFlag(AnalysisType.DoingAc))
                    {
                        throw new AcCurrentPowerException();
                    }
                    else
                    {
                        return ckt.State.Solution[CCCScontBranch] * CCCScoeff;
                    }
                case _c.CCCS_VOLTS:
                    return (ckt.State.Solution[CCCSposNode] -
                    ckt.State.Solution[CCCSnegNode]);
                case _c.CCCS_POWER:
                    if (ckt.CurrentAnalysis.HasFlag(AnalysisType.DoingAc))
                    {
                        throw new AcCurrentPowerException();
                    }
                    else
                    {
                        return ckt.State.Solution[CCCScontBranch] * CCCScoeff *
                                (ckt.State.Solution[CCCSposNode] -
                                ckt.State.Solution[CCCSnegNode]);
                    }
                default:
                    throw new BadParameterException();
            }
        }

        /// <summary>
        /// Setup the device
        /// </summary>
        /// <param name="ckt"></param>
        public override void Setup(Circuit ckt, Model model, object p = null)
        {
            var nodes = BindNodes(ckt);
            CCCSposNode = nodes[0].Number;
            CCCSnegNode = nodes[1].Number;

            // Find the voltage source for which the current is measured
            var models = ckt.FindModels(typeof(VoltageSourceModel));
            CCCScontBranch = -1;
            foreach (VoltageSourceModel mod in models)
            {
                VoltageSource vs = mod.Find(CCCScontName) as VoltageSource;
                if (vs != null)
                    CCCScontBranch = vs.VSRCbranch;
            }
            if (CCCScontBranch < 0)
                throw new CircuitException("Could not find controlling voltage source for " + Name);
        }

        /// <summary>
        /// Do temperature-dependent stuff
        /// </summary>
        /// <param name="ckt"></param>
        public override void Temperature(Circuit ckt, Model model, object parameters = null)
        {
        }

        /// <summary>
        /// Load the cccs in the circuit
        /// </summary>
        /// <param name="ckt"></param>
        public override void Load(Circuit ckt, Model model, object p = null)
        {
            CircuitState state = ckt.State;
            state.Matrix[CCCSposNode, CCCScontBranch] += this.CCCScoeff;
            state.Matrix[CCCSnegNode, CCCScontBranch] -= this.CCCScoeff;
        }

        /// <summary>
        /// Load the cccs in the circuit for AC analysis
        /// </summary>
        /// <param name="ckt"></param>
        public override void AcLoad(Circuit ckt, Model model, object p = null)
        {
            CircuitStateComplex state = ckt.StateComplex;
            state.Matrix[CCCSposNode, CCCScontBranch] += CCCScoeff.Value;
            state.Matrix[CCCSnegNode, CCCScontBranch] -= CCCScoeff.Value;
        }
    }
}
